﻿using Xejen.Logger;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xejen.Database.Entities;

namespace Xejen.Database.Ef
{
    /// <summary>
    /// 表示使用<see langword="Ef"/>框架的数据库上下文 
    /// </summary>
    /// <creator>marc</creator>
    public abstract class EfDbContext : DbContext
    {
        private bool _disposed = false;
        
        /// <inheritdoc cref="EfDbContext"/>
        protected EfDbContext()
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="nameOrConnectionString">表示数据库连接的名字，或者具体的连接字符串</param>
        protected EfDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="model">表示EDM模型，为提高性能，可以缓存这种由EDM创建的DbContext类型对象</param>
        protected EfDbContext(DbCompiledModel model) : base(model)
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="existingConnection">要用于新的上下文的现有连接</param>
        /// <param name="contextOwnsConnection">如果设置为 true，则释放上下文时将释放该连接；否则调用方必须释放该连接</param>
        protected EfDbContext(DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="nameOrConnectionString">表示数据库连接的名字，或者具体的连接字符串</param>
        /// <param name="model">表示EDM模型，为提高性能，可以缓存这种由EDM创建的DbContext类型对象</param>
        protected EfDbContext(string nameOrConnectionString, DbCompiledModel model) : base(nameOrConnectionString, model)
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="existingObjectContext">要用于新的上下文的现有连接</param>
        /// <param name="dbContextOwnsObjectContext">如果设置为 true，则释放上下文时将释放该连接；否则调用方必须释放该连接</param>
        protected EfDbContext(ObjectContext existingObjectContext, bool dbContextOwnsObjectContext) : base(existingObjectContext, dbContextOwnsObjectContext)
        {
            InitializeDbContext();
        }

        /// <inheritdoc cref="EfDbContext"/>
        /// <param name="existingConnection">要用于新的上下文的现有连接</param>
        /// <param name="model">表示EDM模型，为提高性能，可以缓存这种由EDM创建的DbContext类型对象</param>
        /// <param name="contextOwnsConnection">如果设置为 true，则释放上下文时将释放该连接；否则调用方必须释放该连接</param>
        protected EfDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection) : base(existingConnection, model, contextOwnsConnection)
        {
            InitializeDbContext();
        }

        /// <summary>
        /// 初始化数据库上下文对象
        /// </summary>
        private void InitializeDbContext()
        {
            RegisterToChanges();
        }

        private void RegisterToChanges()
        {
            var adapter = ((IObjectContextAdapter)this);
            var context = adapter.ObjectContext;
            var stateManager = context.ObjectStateManager;
            stateManager.ObjectStateManagerChanged += ObjectStateManager_ObjectStateManagerChanged;
        }

        /// <summary>
        /// 表示数据库上下文管理对象的变化事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void ObjectStateManager_ObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
        {
            var contextAdapter = (IObjectContextAdapter)this;
            if (e.Action != CollectionChangeAction.Add)
            {
                return;
            }

            var entry = contextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element);
            switch (entry.State)
            {
                case EntityState.Added:
                    break;
                    //case EntityState.Deleted: //It's not going here at all
                    //    SetDeletionAuditProperties(entry.Entity, GetAuditUserId());
                    //    break;
            }
        }

        /// <summary>
        /// 数据库初始化
        /// </summary>
        public virtual void Initialize()
        {
            Database.Initialize(false);
        }

        /// <summary>
        /// 模型创建中的事件
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }

        /// <summary>
        /// 保存数据的事件
        /// </summary>
        /// <returns></returns>
        public override int SaveChanges()
        {
            try
            {
                var result = base.SaveChanges();
                return result;
            }
            catch (DbEntityValidationException exp)
            {
                LogDbEntityValidationException(exp);
                throw;
            }
        }

        /// <summary>
        /// 保存数据的事件
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
        {
            try
            {
                var result = await base.SaveChangesAsync(cancellationToken);
                return result;
            }
            catch (DbEntityValidationException exp)
            {
                LogDbEntityValidationException(exp);
                throw;
            }
        }

        EdmProperty GetEdmProperty(Type type, string propertyName)
        {
            var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;

            var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

            var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace)
                .Single(t => objectItemCollection.GetClrType(t) == type);

            var entitySet = metadata.GetItems<EntityContainer>(DataSpace.SSpace).Single().EntitySets
                .Single(s => s.ElementType.Name == entityType.Name);

            return entitySet.ElementType.Properties.Single(e =>
                string.Equals(e.Name, propertyName, StringComparison.OrdinalIgnoreCase));
        }

        string GetIdPropertyName(Type type)
        {
            var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;

            var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

            var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace)
                .Single(t => objectItemCollection.GetClrType(t) == type);

            var entitySetCSpace = metadata
                .GetItems<EntityContainer>(DataSpace.CSpace)
                .Single()
                .EntitySets
                .Single(s => s.ElementType.Name == entityType.Name);

            var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
                .Single()
                .EntitySetMappings
                .Single(s => s.EntitySet == entitySetCSpace);

            return mapping
                .EntityTypeMappings.Single()
                .Fragments.Single()
                .PropertyMappings
                .OfType<ScalarPropertyMapping>()
                .Single(m => m.Property.Name == nameof(Entity.Id))
                .Column
                .Name;
        }

        /// <summary>
        /// 数据数据库错误日志
        /// </summary>
        /// <param name="exception"></param>
        protected virtual void LogDbEntityValidationException(DbEntityValidationException exception)
        {
            Debug.WriteLine($"数据库SaveChanges时发生的异常，下面是打印的详情日志：");

            foreach (var ve in exception.EntityValidationErrors.SelectMany(eve => eve.ValidationErrors))
            {
                Debug.WriteLine(" - " + ve.PropertyName + ": " + ve.ErrorMessage);
            }

            //toto:记录数据库日志
        }

        /// <inheritdoc/>
        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // 注销事件处理程序
                    var adapter = (IObjectContextAdapter)this;
                    var context = adapter.ObjectContext;
                    var stateManager = context.ObjectStateManager;
                    stateManager.ObjectStateManagerChanged -= ObjectStateManager_ObjectStateManagerChanged;
                }
                _disposed = true;
            }

            base.Dispose(disposing);
        }
    }
}
